using DemoRenderer;
using BepuPhysics;
using BepuPhysics.Collidables;
using BepuUtilities;
using System;
using System.Diagnostics;
using DemoUtilities;
using DemoRenderer.UI;
using OpenTK.Input;
#if MYCODE
using BepuUtilities.Vectors;
#else
using System.Numerics;
#endif

namespace Demos.Demos.Characters
{
    /// <summary>
    /// 包装CharacterController引用及其关联正文的便捷结构。
    /// </summary>
    /// <remarks>
    /// <para>这应该作为示例处理-这里没有任何内容是为了建议您"必须"如何处理字符。
    /// 相反,如果您以一种可预测的方式处理数百个字符,这会造成一些相当低效的事情。
    /// 对于演示使用来说,这只是一个相当方便的界面。</para>
    /// <para>请注意,所有角色都是动态的,并且响应模拟中的约束和力。</para>
    /// </remarks>
    public struct CharacterInput
    {
        BodyHandle bodyHandle;
        CharacterControllers characters;
        float speed;
        Capsule shape;

        public BodyHandle BodyHandle { get { return bodyHandle; } }

        public CharacterInput(CharacterControllers characters, Vector3 initialPosition, Capsule shape,
            float speculativeMargin, float mass, float maximumHorizontalForce, float maximumVerticalGlueForce,
            float jumpVelocity, float speed, float maximumSlope = MathF.PI * 0.25f)
        {
            this.characters = characters;
            var shapeIndex = characters.Simulation.Shapes.Add(shape);

            // 因为角色是动态的,所以它们需要定义的BodyInertia。出于演示的目的,我们不希望它们旋转或倒下,因此逆惯性张量保留为默认值全零。
            // 这实际上相当于给它一个无限的惯性张量--换句话说,没有任何力矩会使它旋转。
            bodyHandle = characters.Simulation.Bodies.Add(BodyDescription.CreateDynamic(initialPosition, new BodyInertia { InverseMass = 1f / mass }, new CollidableDescription(shapeIndex, speculativeMargin), new BodyActivityDescription(shape.Radius * 0.02f)));
            ref var character = ref characters.AllocateCharacter(bodyHandle);
            character.LocalUp = new Vector3(0, 1, 0);
            character.CosMaximumSlope = MathF.Cos(maximumSlope);
            character.JumpVelocity = jumpVelocity;
            character.MaximumVerticalForce = maximumVerticalGlueForce;
            character.MaximumHorizontalForce = maximumHorizontalForce;
            character.MinimumSupportDepth = shape.Radius * -0.01f;
            character.MinimumSupportContinuationDepth = -speculativeMargin;
            this.speed = speed;
            this.shape = shape;
        }

        static Key MoveForward = Key.W;
        static Key MoveBackward = Key.S;
        static Key MoveRight = Key.D;
        static Key MoveLeft = Key.A;
        static Key Sprint = Key.LShift;
        static Key Jump = Key.Space;
        static Key JumpAlternate = Key.BackSpace; // 我有一个奇怪的键盘。

        public void UpdateCharacterGoals(Input input, Camera camera, float simulationTimestepDuration)
        {
            Vector2 movementDirection = default;
            if (input.IsDown(MoveForward))
            {
                movementDirection = new Vector2(0, 1);
            }
            if (input.IsDown(MoveBackward))
            {
                movementDirection += new Vector2(0, -1);
            }
            if (input.IsDown(MoveLeft))
            {
                movementDirection += new Vector2(-1, 0);
            }
            if (input.IsDown(MoveRight))
            {
                movementDirection += new Vector2(1, 0);
            }
            var movementDirectionLengthSquared = movementDirection.LengthSquared();
            if (movementDirectionLengthSquared > 0)
            {
                movementDirection /= MathF.Sqrt(movementDirectionLengthSquared);
            }

            ref var character = ref characters.GetCharacterByBodyHandle(bodyHandle);
            character.TryJump = input.WasPushed(Jump) || input.WasPushed(JumpAlternate);
            var characterBody = new BodyReference(bodyHandle, characters.Simulation.Bodies);
            var effectiveSpeed = input.IsDown(Sprint) ? speed * 1.75f : speed;
            var newTargetVelocity = movementDirection * effectiveSpeed;
            var viewDirection = camera.Forward;
            // 修改角色的原始数据不会自动唤醒角色,因此如果需要,我们会显式执行此操作。
            // 如果不显式唤醒角色,它将不会响应更改的运动目标。
            // (也可以在BodyActivityDescription中指定负的停用阈值,以完全阻止角色睡眠。)
            if (!characterBody.Awake &&
                ((character.TryJump && character.Supported) ||
                newTargetVelocity != character.TargetVelocity ||
                (newTargetVelocity != Vector2.Zero && character.ViewDirection != viewDirection)))
            {
                characters.Simulation.Awakener.AwakenBody(character.BodyHandle);
            }
            character.TargetVelocity = newTargetVelocity;
            character.ViewDirection = viewDirection;

            // 当角色在空中时,角色的运动约束是不活动的,所以如果我们想要空气控制,我们需要自己应用它。
            // (您也可以修改约束来执行此操作,但对于空气控制而言,已求解约束的健壮性往往不那么重要。)
            // 没有任何一种"正确"的方式来实现空中控制--这是一种非物理的游戏玩法,而这只是其中一种方式。
            // 请注意,这允许沿特定方向加速,并且从不尝试减速角色。
            // 这允许一些在某些游戏角色控制器中常见的移动怪癖。
            // 考虑一下,如果从静止状态开始,你沿着X完全加速,然后沿着Z,你的全速大小是sqrt(2)*maxumAirSpeed,会发生什么。
            // 您可以随意尝试其他实现方式。再说一次,没有一种正确的方法。
            if (!character.Supported && movementDirectionLengthSquared > 0)
            {
                QuaternionEx.Transform(character.LocalUp, characterBody.Pose.Orientation, out var characterUp);
                var characterRight = Vector3.Cross(character.ViewDirection, characterUp);
                var rightLengthSquared = characterRight.LengthSquared();
                if (rightLengthSquared > 1e-10f)
                {
                    characterRight /= MathF.Sqrt(rightLengthSquared);
                    var characterForward = Vector3.Cross(characterUp, characterRight);
                    var worldMovementDirection = characterRight * movementDirection.X + characterForward * movementDirection.Y;
                    var currentVelocity = Vector3.Dot(characterBody.Velocity.Linear, worldMovementDirection);
                    // 我们会任意将空气控制设置为支撑运动速度/力的一小部分。
                    const float airControlForceScale = .2f;
                    const float airControlSpeedScale = .2f;
                    var airAccelerationDt = characterBody.LocalInertia.InverseMass * character.MaximumHorizontalForce * airControlForceScale * simulationTimestepDuration;
                    var maximumAirSpeed = effectiveSpeed * airControlSpeedScale;
                    var targetVelocity = MathF.Min(currentVelocity + airAccelerationDt, maximumAirSpeed);
                    // 虽然我们不应该允许角色在空中无限期地加速,但尝试朝给定的方向移动永远不应该减慢我们在那个方向上的速度。
                    var velocityChangeAlongMovementDirection = MathF.Max(0, targetVelocity - currentVelocity);
                    characterBody.Velocity.Linear += worldMovementDirection * velocityChangeAlongMovementDirection;
                    Debug.Assert(characterBody.Awake, "Velocity changes don't automatically update objects; the character should have already been woken up before applying air control.");
                }
            }
        }

        public void UpdateCameraPosition(Camera camera, float cameraBackwardOffsetScale = 4)
        {
            // 我们将通过将摄影机附加到角色控制器主体来覆盖演示线束的摄影机控件。
            ref var character = ref characters.GetCharacterByBodyHandle(bodyHandle);
            var characterBody = new BodyReference(bodyHandle, characters.Simulation.Bodies);
            // 使用简单的颈部模型,这样当摄影机向下看时,屏幕的中心可以看到角色之外的部分。
            // 使鼠标锁定的光线拾取更容易。
            camera.Position = characterBody.Pose.Position + new Vector3(0, shape.HalfLength, 0) +
                camera.Up * (shape.Radius * 1.2f) -
                camera.Forward * (shape.HalfLength + shape.Radius) * cameraBackwardOffsetScale;
        }

        void RenderControl(ref Vector2 position, float textHeight, string controlName, string controlValue, TextBuilder text, TextBatcher textBatcher, Font font)
        {
            text.Clear().Append(controlName).Append(": ").Append(controlValue);
            textBatcher.Write(text, position, textHeight, new Vector3(1), font);
            position.Y += textHeight * 1.1f;
        }
        public void RenderControls(Vector2 position, float textHeight, TextBatcher textBatcher, TextBuilder text, Font font)
        {
            RenderControl(ref position, textHeight, nameof(MoveForward), MoveForward.ToString(), text, textBatcher, font);
            RenderControl(ref position, textHeight, nameof(MoveBackward), MoveBackward.ToString(), text, textBatcher, font);
            RenderControl(ref position, textHeight, nameof(MoveRight), MoveRight.ToString(), text, textBatcher, font);
            RenderControl(ref position, textHeight, nameof(MoveLeft), MoveLeft.ToString(), text, textBatcher, font);
            RenderControl(ref position, textHeight, nameof(Sprint), Sprint.ToString(), text, textBatcher, font);
            RenderControl(ref position, textHeight, nameof(Jump), Jump.ToString(), text, textBatcher, font);
        }


        /// <summary>
        /// 从模拟中移除角色的躯干,并从关联角色集中移除角色。
        /// </summary>
        public void Dispose()
        {
            characters.Simulation.Shapes.Remove(new BodyReference(bodyHandle, characters.Simulation.Bodies).Collidable.Shape);
            characters.Simulation.Bodies.Remove(bodyHandle);
            characters.RemoveCharacterByBodyHandle(bodyHandle);
        }
    }
}


